Skip to content

fix(app-shell): drop the undeclared chart.config forward on the object-chart node (objectui#7891) - #7948

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7891-objectview-chart-config-forward
Sep 6, 2026
Merged

fix(app-shell): drop the undeclared chart.config forward on the object-chart node (objectui#7891)#7948
os-sam merged 1 commit into
mainfrom
claude/issue-7891-objectview-chart-config-forward

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #7891

Clause-②: no

ObjectView's dedicated chart-view route put config: chartConfig.config on the
object-chart schema it builds, on both branches — :2118 (ADR-0021 dataset) and :2152
(legacy). This drops both rungs, and the two as any casts that turned out to be inert.

The card's premise was inverted before this PR, and the inversion is the fix

The card was filed as "ListView is missing a config rung that the dedicated chart route
has". The measurement that preceded this work reversed it: ListView is not missing a
channel — app-shell had an extra one no author is allowed to feed.

@objectstack/spec@17.2.0's ListChartConfigSchema is a strictObject whose declared
keys are exactly chartType / dataset / dimensions / values, and objectui binds it
BY REFERENCE (chart is absent from LIST_VIEW_LOCAL_OVERRIDES,
packages/types/src/zod/objectql.zod.ts:312), so ListViewSchema.chart IS the spec's
strict schema. A view body carrying the key is refused by name.

So this was never one authored declaration rendering two ways. It was an asymmetry between
two UNDECLARED channels — and ObjectChartSchema does not declare config either, so the
key landed on a node that had no name for it.

The blocking measurement this dispatch required: can chart.config reach STORED data?

No — the write door refuses it, and no producer exists. Four readings:

  1. The write door parses every save through the same schema.
    saveMetaItem (objectstack packages/metadata-protocol/src/protocol.ts:15149) calls
    resolveOverlaySchema(type) to getMetadataTypeSchema('view')
    (objectstack packages/spec/src/kernel/metadata-type-schemas.ts:110), safeParses, and
    answers 422 INVALID_METADATA on failure. There is no early return between the mode
    fold and that gate, and the gate reads neither mode nor force — so it runs on
    mode: 'draft' (what app-shell's persistRuntimeMetadata sends) exactly as on publish.
    Run against the published spec build this repo resolves, from inside its real dependency
    graph:

    getMetadataTypeSchema present = function
    view schema resolved          = true
    CONTROL (no config)  success = true
    WITH chart.config    success = false
      unrecognized_keys keys=["config"] path=["config","chart"]
      message=Unrecognized key(s) on this list chart configuration: `config`. Until #4001
      closed these shapes an unknown key was dropped silently — the view still rendered,
      without whatever the key was meant to configure.
    
  2. The authoring UIs cannot mint it. ViewConfigPanel hosts the studio's
    ViewVariantInspector, which renders the per-view-type fields straight from
    @objectstack/spec; CreateViewDialog's chart field list is chartType /
    xAxisField / yAxisFields. view-config-adapter round-trips unknown keys, so it would
    PRESERVE a stored one — but it originates nothing, and a save carrying one would now hit
    the 422 above.

  3. No producer exists in this repo. config: chartConfig.config at :2118 / :2152
    were the only two occurrences of the pattern anywhere in the tree.

  4. No stored fixture carries it. A structural scan (any object at key chart owning a
    config key, at any depth) over every tracked JSON file: 0 hits in objectui (544
    files parsed of 6485 tracked) and 0 in objectstack (444 parsed).

Legacy rows could in principle predate the #4001 tightening — which is exactly the risk the
next section bounds — but nothing measurable here produces or holds one.

Degrade, not break

packages/plugin-charts/src/ChartRenderer.tsx:151-160 auto-generates a container config
from series plus a positional palette when config is absent, so any non-conforming row
that somehow existed still renders — it falls back to series-derived labels and default
chart colours. The spec's own refusal message says the same thing about the class:

Until #4001 closed these shapes an unknown key was dropped silently — the view still
rendered, without whatever the key was meant to configure.

Why nothing ever caught this, and why the casts went too

Not the as any. One layer further out: ObjectChart is published as (props: any)
(packages/plugin-charts/src/ObjectChart.tsx:275, and verbatim in its .d.ts), so those
literals were checked against nothing whatever. MEASURED: with BOTH casts removed,
tsc --noEmit is green over a program --listFiles confirms contains ObjectView.tsx.
The casts were inert. Leaving one sitting exactly where the defect lived is what invites
the next reader to assume the shape was checked, so they are gone with the rungs, and a
comment block records the reasoning in place.

That prop-type gap is one seam out and in two other packages, so it is reported, not fixed
here: objectui#7946.

The pin

ObjectView.chartConfigForward-7891.test.tsx reads the schema ObjectView HANDS DOWN, one
arm per branch, plus a positive control per branch so an absence assertion cannot go green
on a mount that rendered nothing. The assertion is 'config' in schema, not
toBeUndefined() — the removed line emitted a PRESENT config: undefined key on every
chart view whether or not an author wrote one, so toBeUndefined() would have passed
against the defect itself.

Reverse verification, direction written before the run: re-injecting
config: chartConfig.config, into one literal was predicted to turn that branch's two
absence arms RED and leave the other four arms green.

  • dataset branch re-injected: Tests 2 failed | 4 passed (6), the two failures being the
    dataset absence arms.
  • legacy branch re-injected: Tests 2 failed | 4 passed (6), the two failures being the
    legacy absence arms.

Each leg confirmed the mutation on disk before reading (marker-count before/after, plus the
diff line), and each restored by STATE — git diff HEAD empty and the blob back to the HEAD
blob ef1b7e36675ace83a3d55520149cbc2119a8b85b — never by an exit code. The legacy leg's
FIRST attempt refused to run: its marker text is a substring of the dataset branch's
more-indented copy, the anchor assertion caught the ambiguity, and the script reported
"mutation did NOT land on disk" instead of silently mutating the wrong branch. Re-anchored
on surrounding lines and re-run.

Verification, all at e33c0c329

check result
vitest run packages/app-shell/ (repo root, RUN v4.1.10 /home/user/objectui-issue-7891) Test Files 627 passed (627) · Tests 5995 passed, 1 skipped (5996)
the pin + the objectui#7823 relay pin Test Files 2 passed (2) · Tests 14 passed (14)
pnpm --filter @object-ui/app-shell run type-check (tsc --noEmit and tsc -p tsconfig.test.json) exit 0, script name echoed
check:control-bytes OK, 6402 tracked text files scanned
check:vi-mock-specifiers / check:vi-mock-inherit OK
check:phantom-deps / check:self-import / check:esm-specifiers / check:unreferenced-sources OK
node scripts/check-changeset-presence.mjs green, verdict quoted below

check-changeset-presence, verbatim:

2 source file(s) of 1 released package(s) changed, and this change declares 1
changeset(s): .changeset/olive-donkeys-invent.md.

Lint — a declared narrowing, with its evidence. eslint . --no-inline-config was run
whole rather than narrowed: 4332 files linted, 0 errors in either changed file under a
configuration stricter than CI's (it disables the eslint-disable-next-line comments CI
honours). The 93 errors it reports are the repo's pre-existing baseline under that flag,
across 77 other files. The CI-equivalent targeted run on the two changed files is 0 errors,
172 warnings, all no-explicit-any / react-refresh of the kind the lint workflow
deliberately does not cap. The config enables no type-aware linting (no project /
projectService in eslint.config.js), so this diff cannot move any verdict on a file it
did not touch.

Scope

packages/app-shell/src/views/ObjectView.tsx plus one pin test in the same package, and
the changeset. Nothing in packages/plugin-list/, packages/plugin-charts/,
packages/types/, or the objectstack repo — which was read only. In particular no config
rung was added to ListView, and no published schema was changed: declaring the key would
mint a second declaration of one that already exists, declared and documented, on the
standalone SDUI type: 'chart' component
(packages/types/src/zod/sdui/data-display.zod.ts:664).

Deleting a key the protocol already refuses by name changes no schema's accept/reject
behaviour, widens no public surface, and moves no gate's scan population — hence
Clause-②: no.

Related, none of them addressed here: objectui#7823 and its PR only made this reachable
(before them the block did not arrive on that route with a usable binding at all, so this
was not a regression they introduced); objectui#7946 is the prop-type gap above.

🤖 Generated with Claude Code


Generated by Claude Code

…ect-chart node (objectui#7891)

`ObjectView`'s dedicated chart-view route put `config: chartConfig.config` on the
`object-chart` schema it builds, on both the ADR-0021 dataset branch and the legacy
branch. `@objectstack/spec`'s `ListChartConfigSchema` is a `strictObject` declaring
exactly `chartType` / `dataset` / `dimensions` / `values`, and objectui binds it by
reference, so `config` is refused by name — `unrecognized_keys ["config"]` — by the same
schema the platform's metadata write door parses every save through. The rung was a
channel no conforming author could feed, landing on a node that does not declare `config`
either.

The `as any` on both literals was measured inert: `ObjectChart` is published as
`(props: any)`, so the literals were checked against nothing. Removed with the rung.

For every spec-conforming view this is a no-op. A non-conforming row degrades rather than
breaks — `ChartRenderer` generates a container config from `series` plus a positional
palette when none is present.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3189.3 KB 3191.4 KB
Main entry chunk (gzip) 143.5 KB 350 KB
Entry file index-CaK5N0ZF.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.60KB 116.20KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 182.08KB 50.62KB
fields (index.js) 242.44KB 61.25KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.37KB
plugin-dashboard (index.js) 132.88KB 34.69KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.56KB 27.70KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.59KB 11.97KB
plugin-timeline (index.js) 30.40KB 8.76KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-sam
os-sam marked this pull request as ready for review September 6, 2026 04:19
@os-sam
os-sam added this pull request to the merge queue Sep 6, 2026
Merged via the queue into main with commit 38158c6 Sep 6, 2026
34 checks passed
@os-sam
os-sam deleted the claude/issue-7891-objectview-chart-config-forward branch September 6, 2026 04:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant